package com.bub.pay.framework.shiro.manager;

import com.alibaba.fastjson.JSON;
import com.bub.pay.framework.common.entity.model.RespObject;
import com.bub.pay.framework.common.exception.GlobalException;
import com.bub.pay.framework.common.util.ContextHolder;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.ExecutionException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanInitializationException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.Callable;

import static com.bub.pay.framework.common.base.Cause.unknown;

public class BNShiroFilterFactoryBean extends ShiroFilterFactoryBean {

    private static transient final Logger log = LoggerFactory.getLogger(BNShiroFilterFactoryBean.class);


    protected AbstractShiroFilter createInstance() throws Exception {

        log.debug("Creating Shiro Filter instance.");

        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        //Expose the constructed FilterChainManager by first wrapping it in a
        // FilterChainResolver implementation. The AbstractShiroFilter implementations
        // do not know about FilterChainManagers - only resolvers:
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
        //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
        //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
        //injection of the SecurityManager and FilterChainResolver:
        return new BNSpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

    private static final class BNSpringShiroFilter extends AbstractShiroFilter {

        protected BNSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            super();
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            }
            setSecurityManager(webSecurityManager);
            if (resolver != null) {
                setFilterChainResolver(resolver);
            }
        }

        protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
                throws ServletException, IOException {

            Throwable t = null;

            try {
                final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
                final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

                //自定义给sessionDao中获取request
                BNWebSubjectContext.currentRequest.set((HttpServletRequest)request);
                ContextHolder.clearRequestDataMap();
                ContextHolder.setServletRequest((HttpServletRequest) request);

                final Subject subject = createSubject(request, response);

                //noinspection unchecked
                subject.execute(new Callable() {
                    public Object call() throws Exception {
                        updateSessionLastAccessTime(request, response);
                        executeChain(request, response, chain);
                        return null;
                    }
                });
            } catch (ExecutionException ex) {
                t = ex.getCause();
            } catch (Throwable throwable) {
                t = throwable;
            }

            if (t != null) {
                log.error("shiroError",t);
                servletResponse.setContentType("application/json;charset=UTF-8");
                if(t instanceof NullPointerException){
                    servletResponse.getOutputStream().write(JSON.toJSONBytes(new RespObject(new GlobalException(unknown))));
                    return;
                }
                if(t instanceof GlobalException){
                    servletResponse.getOutputStream().write(JSON.toJSONBytes(new RespObject((GlobalException) t)));
                    return;
                }
                if (t instanceof ServletException) {
                    throw (ServletException) t;
                }
                if (t instanceof IOException) {
                    throw (IOException) t;
                }
                //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
                String msg = "Filtered request failed.";
                throw new ServletException(msg, t);
            }
        }
    }
}
